﻿/*===========================================================================================
 * 
 *  Author          : Arturo Salvamante
 * 
 *  File            : PCSCMifare.cs
 * 
 *  Copyright (C)   : Advanced Card System Ltd
 * 
 *  Description     : Contains Methods and Properties for ACR1222U SmartCard Reader
 * 
 *  Date            : June 03, 2011
 * 
 *  Revision Traile : [Author] / [Date if modification] / [Details of Modifications done]
 * 
 * 
 * =========================================================================================*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ACS
{
    public enum MIFARE_TYPE
    {
        Mifare_1K = 0,
        Mifare_4K = 1
    }

    public enum MIFARE_ERROR_CODES
    {
        READ_VALUE_FAILED = 1,
        READ_BINARY_FAILED = 2,
        WRITE_VALUE_FAILED = 3,
        WRITE_BINARY_FAILED = 4,
        STORE_VALUE_FAILED = 5,
        INCREMENT_FAILED = 6,
        DECREMENT_FAILED = 7,
        RESTORE_VALUE_FAILED = 8
    }

    public class PcscMifare
    {
        public PcscMifare(PcscItem pcscConnection)
        {
            _PcscConnection = pcscConnection;
        }

        private PcscItem _PcscConnection;
        public PcscItem pcscConnection
        {
            get { return _PcscConnection; }
            set { _PcscConnection = value; }
        }

        public enum KEYTYPES
        {
            ACR122_KEYTYPE_A = 0x60,
            ACR122_KEYTYPE_B = 0x61
        }

        public enum VALUEBLOCKOPERATION
        {
            STORE = 0x00,
            INCREMENT = 0x01,
            DECREMENT = 0x02
        }

        public int valueBlock(byte blockNum, VALUEBLOCKOPERATION transType, int amount)
        {
            Apdu apdu;
            int returnCode;

            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0xD7, 0x00, blockNum, 0x05 });

            apdu.sendData = new byte[5];
            apdu.sendData[0] = (byte)transType;
            Array.Copy(Helper.intToByte(amount), 0, apdu.sendData, 1, 4);

            returnCode =  pcscConnection.sendApdu(ref apdu);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(returnCode);

            if (apdu.sw[0] != 0x90)
            {
                MIFARE_ERROR_CODES c = MIFARE_ERROR_CODES.STORE_VALUE_FAILED;
                switch(transType)
                {
                    case VALUEBLOCKOPERATION.DECREMENT:c = MIFARE_ERROR_CODES.DECREMENT_FAILED; break;
                    case VALUEBLOCKOPERATION .INCREMENT:c= MIFARE_ERROR_CODES.INCREMENT_FAILED; break;
                    case VALUEBLOCKOPERATION.STORE: c = MIFARE_ERROR_CODES.STORE_VALUE_FAILED; break;
                }
                return (int)c;
            }

            return 0;
        }

        public int restoreValueBlock(byte sourceBlock, byte targetBlock)
        {
            Apdu apdu;
            int returnCode;
            
            apdu = new Apdu();
            apdu.lengthExpected = 2;

            apdu.setCommand(new byte[] { 0xFF, 0xD7, 0x00, sourceBlock, 0x02 });

            apdu.sendData = new byte[2];
            apdu.sendData[0] = 0x03;
            apdu.sendData[1] = targetBlock;

            returnCode = pcscConnection.sendApdu(ref apdu);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                return returnCode;

            if (apdu.sw[0] != 0x90)
                return (int)MIFARE_ERROR_CODES.READ_VALUE_FAILED;

            return 0;

        }

        public byte[] readValue(byte blockNum)
        {
            Apdu apdu;
            int returnCode;


            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0xB1, 0x00, blockNum, 0x04 });
            apdu.lengthExpected = 4;

            returnCode = pcscConnection.sendApdu(ref apdu);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(returnCode);

            if (apdu.sw[0] != 0x90)
                return null;

            return apdu.receiveData;
        }

        public byte[] readBinaryBlock(byte blockNum)
        {
            return readBinaryBlock(blockNum, 16);
        }

        public byte[] readBinaryBlock(byte blockNum, byte lengthToRead)
        {
            Apdu apdu;
            int returnCode;


            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0xB0, 0x00, blockNum, lengthToRead });
            apdu.sendData = new byte[0];
            apdu.lengthExpected = lengthToRead + 2;

            returnCode = pcscConnection.sendApdu(ref apdu);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(returnCode);

            if (apdu.sw[0] != 0x90)
                throw new Exception("read binary block failed");

            return apdu.receiveData.Take(lengthToRead).ToArray();
        }

        public int updateBinaryBlock(byte blockNum, byte[] data)
        {
            Apdu apdu;
            int returnCode;

            if (data.Length > 48)
                throw new Exception("Data has invalid length");

            apdu = new Apdu();
            apdu.setCommand(new byte[] { 0xFF, 0xD6, 0x00, blockNum, (byte)data.Length });
            apdu.lengthExpected = 2;

            apdu.sendData = new byte[data.Length];
            Array.Copy(data, apdu.sendData, data.Length);
            
            returnCode = pcscConnection.sendApdu(ref apdu);
            if (returnCode != PCSCProvider.SCARD_S_SUCCESS)
                throw new PcscException(returnCode);

            if (apdu.sw[0] != 0x90)
                return (int)MIFARE_ERROR_CODES.WRITE_BINARY_FAILED;

            return 0;
        }

        public string getErrorMessage(byte[] sw1sw2)
        {
            if (sw1sw2.Length < 2)
                return "Unknow Status Word (" +  Helper.byteAsString(sw1sw2, false) + ")";

            else if (sw1sw2[0] == 0x63 && sw1sw2[1] == 0x00)
                return "Command failed";
            else
                return "Unknow Status Word (" + Helper.byteAsString(sw1sw2, false) + ")";
        }

        public static bool isMifare(byte[] atr)
        {
            if (atr != null && atr.Length > 8 && Helper.byteArrayIsEqual(atr.Skip(4).Take(3).ToArray(), new byte[] { 0x80, 0x4F, 0x0C }))
                return true;
            else
                return false;
        }
    }
}
